package com.hxzy.springbootes.controller;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hxzy.springbootes.es.entity.EsHotel;
import com.hxzy.springbootes.es.service.EsHotalService;
import com.hxzy.springbootes.mybatis.entity.TbHotel;
import com.hxzy.springbootes.mybatis.service.TbHotelService;
import com.hxzy.springbootes.req.HotelReq;
import com.hxzy.springbootes.resp.HotelResp;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author gityyge
 * @Description DSL搜索
 * @Date 2023/5/31 15:55
 * @Version 1.0
 */
@RestController
public class EsHotalSearchController {


    /**
     * DSL查询
     */
    @Autowired
    private RestHighLevelClient restHighLevelClient;


    /**
     * 根据名称查询
     * @param page
     * @param keyword
     * @return
     * @throws IOException
     */
    @GetMapping(value = "/es/search/byName")
    public Map<String,Object> searchByName(int page, String keyword) throws IOException {

        ObjectMapper  objectMapper=new ObjectMapper();
        //反序列化，字段没有，直接忽略
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);


        // 1.准备Request
        SearchRequest request = new SearchRequest("test_hotel");
        // 2.准备DSL(查询条件是什么，要不要分页，要不要排序，要不要高亮)
        //分页  相当于数据库  limit   offset ,  size
        int offset= (page-1)*10;
        request.source()
                //构建查询条件QueryBuilders来配置
                .query(QueryBuilders.matchQuery("all",keyword))
                //分页排除前几笔
                .from(offset)
                //分页取几笔
                .size(10);


        // 3.发送ES请求 rest api请求
        SearchResponse response = this.restHighLevelClient.search(request, RequestOptions.DEFAULT);

        //4、得到结果
        SearchHits hits = response.getHits();

        //5、得到总笔数
        long count=hits.getTotalHits().value;


        //6、得到明细
        List<EsHotel> arrList=new ArrayList<>();

        SearchHit[] hitsHits = hits.getHits();
        //7、循环再一次读取数据
        for (SearchHit  ht : hitsHits ) {
            // 获取文档source
            String json = ht.getSourceAsString();
            // 反序列化
            /*
            "_source" : {
              "business" : "江湾、五角场商业区",
              "price" : 349,
              "city" : "上海",
              "@version" : "1",
              "pic" : "https://m.tuniucdn.com/fb2/t1/G2/M00/C7/E3/Cii-T1knFXCIJzNYAAFB8-uFNAEAAKYkQPcw1IAAUIL012_w200_h200_c1_t0.jpg",
              "address" : "杨浦国权路315号",
              "brand" : "7天酒店",
              "@timestamp" : "2023-08-04T07:18:01.044Z",
              "id" : 39141,
              "starname" : "二钻",
              "name" : "7天连锁酒店(上海五角场复旦同济大学店)",
              "score" : 38,
              "location" : "31.290057,121.508804"
            }
             */
            EsHotel esHotel = objectMapper.readValue(json, EsHotel.class);
           arrList.add(esHotel);
        }

        //8、组装自定义对象
        Map<String,Object>  map=new HashMap<>();
        map.put("total", count);
        map.put("rows", arrList);
        return  map;
    }


    /**  GET http://localhost:8080/es/search?page=1&size=10&keyword=酒店&price=200-500&sorted=2&city=深圳
     * 复杂条件查询
     * @param req
     * @return
     */
    @GetMapping(value = "/es/search")
    public HotelResp searchByCondition(HotelReq req) throws IOException {
        ObjectMapper  objectMapper=new ObjectMapper();
        //反序列化，字段没有，直接忽略
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");

        //2、bool条件组装
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //得到一个 and 条件集合
        List<QueryBuilder> mustList = boolQueryBuilder.must();
        if(req.getKey()!=null && req.getKey()!=""){
            //match检索
            mustList.add(QueryBuilders.matchQuery("all",req.getKey()));
        }
        //城市不为空
        if(req.getCity()!=null && req.getCity()!=""){
            //精确匹配  term
            mustList.add(QueryBuilders.termQuery("city", req.getCity()));
        }
        //品牌不为空
        if(req.getBrand()!=null && req.getBrand()!=""){
            //精确匹配  term
            mustList.add(QueryBuilders.termQuery("brand", req.getBrand()));
        }
        //选择了几星
        if(req.getStarName()!=null && req.getStarName()!=""){
            //精确匹配  term
            mustList.add(QueryBuilders.termQuery("starname", req.getStarName()));
        }

        //选择了价格    价格0-150, 100-300 ,   1500
        if(req.getPrice()!=null && req.getPrice()!="") {
            int min=0;
            int max=0;
            RangeQueryBuilder priceBuilder=null;
            String[] priceArr = req.getPrice().split("-");
            if(priceArr.length==1){
               max=Integer.parseInt(priceArr[0]);
               priceBuilder = QueryBuilders.rangeQuery("price").gte(max);

            }else{
                min=Integer.parseInt(priceArr[0]);
                max=Integer.parseInt(priceArr[1]);
                priceBuilder=QueryBuilders.rangeQuery("price").gte(min).lte(max);
            }
            boolQueryBuilder.filter(priceBuilder);
        }

        //一个条件都没有
        if(boolQueryBuilder.must().size()==0){
            throw new RuntimeException("抱歉，没有找到与关键词相关的商品");
        }else{
            request.source().query(boolQueryBuilder);
        }


        //排序 1价格升序， 2价格降序，3评分高，4评分低
        if(req.getSorted()!=null){
           if(req.getSorted()==1){
               request.source().sort("price", SortOrder.ASC);
           }else if(req.getSorted()==2){
               request.source().sort("price", SortOrder.DESC);
           }else if(req.getSorted()==3){
               request.source().sort("score", SortOrder.DESC);
           }else if(req.getSorted()==4){
               request.source().sort("price", SortOrder.ASC);
           }
        }else{
            request.source().sort("starName", SortOrder.ASC);
        }

        //排序
        request.source().from( (req.getPage()-1) * req.getSize());
        request.source().size(req.getSize());

        //查询
        // 3.发送请求
        SearchResponse response = this.restHighLevelClient.search(request,RequestOptions.DEFAULT);

        //4、得到结果
        SearchHits hits = response.getHits();

        //5、得到总笔数
        long count=hits.getTotalHits().value;

        //6、得到明细
        List<EsHotel> arrList=new ArrayList<>();

        SearchHit[] hitsHits = hits.getHits();
        //7、循环再一次读取数据
        for (SearchHit  ht : hitsHits ) {
            // 获取文档source
            String json = ht.getSourceAsString();
            // 反序列化
            EsHotel esHotel = objectMapper.readValue(json, EsHotel.class);
            arrList.add(esHotel);
        }

        //装组对象
        HotelResp resp=new HotelResp();
        resp.setTotal(count);
        resp.setRows(arrList);

        return resp;
    }


    /**
     * 查询 name变高亮
     * @param req
     * @return
     * @throws IOException
     */
    @PostMapping(value = "/es/search/highlight")
    public HotelResp searchByConditionHighLight( @RequestBody HotelReq req) throws IOException {
        ObjectMapper  objectMapper=new ObjectMapper();
        //反序列化，字段没有，直接忽略
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");

        //2、bool条件组装
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //得到一个 and 条件集合
        List<QueryBuilder> mustList = boolQueryBuilder.must();
        if(req.getKey()!=null && req.getKey()!=""){
            //match检索
            mustList.add(QueryBuilders.matchQuery("all",req.getKey()));
        }
        //城市不为空
        if(req.getCity()!=null && req.getCity()!=""){
            //精确匹配  term
            mustList.add(QueryBuilders.termQuery("city", req.getCity()));
        }
        //品牌不为空
        if(req.getBrand()!=null && req.getBrand()!=""){
            //精确匹配  term
            mustList.add(QueryBuilders.termQuery("brand", req.getBrand()));
        }
        //选择了几星
        if(req.getStarName()!=null && req.getStarName()!=""){
            //精确匹配  term
            mustList.add(QueryBuilders.termQuery("starname", req.getStarName()));
        }

        //选择了价格    价格0-150, 100-300 ,   1500
        if(req.getPrice()!=null && req.getPrice()!="") {
            int min=0;
            int max=0;
            RangeQueryBuilder priceBuilder=null;
            String[] priceArr = req.getPrice().split("-");
            if(priceArr.length==1){
                max=Integer.parseInt(priceArr[0]);
                priceBuilder = QueryBuilders.rangeQuery("price").gte(max);

            }else{
                min=Integer.parseInt(priceArr[0]);
                max=Integer.parseInt(priceArr[1]);
                priceBuilder=QueryBuilders.rangeQuery("price").gte(min).lte(max);
            }
            boolQueryBuilder.filter(priceBuilder);
        }

        //一个条件都没有
        if(boolQueryBuilder.must().size()==0){
            //throw new RuntimeException("抱歉，没有找到与关键词相关的商品");
            //查询所有的条件
            request.source().query(QueryBuilders.matchAllQuery());

        }else{
            request.source().query(boolQueryBuilder);
        }


        //排序 1价格升序， 2价格降序，3评分高，4评分低
        if(req.getSorted()!=null){
            if(req.getSorted()==1){
                request.source().sort("price", SortOrder.ASC);
            }else if(req.getSorted()==2){
                request.source().sort("price", SortOrder.DESC);
            }else if(req.getSorted()==3){
                request.source().sort("score", SortOrder.DESC);
            }else if(req.getSorted()==4){
                request.source().sort("price", SortOrder.ASC);
            }
        }else{
            request.source().sort("starName", SortOrder.ASC);
        }

        //排序
        request.source().from( (req.getPage()-1) * req.getSize());
        request.source().size(req.getSize());

        //设定高亮的条件和前缀和后缀 html标签
//        "highlight": {
//            "fields":{
//                "name": {
//                    "require_field_match": "false",
//                            "pre_tags":"<font class='skcolor_ljg'>",
//                            "post_tags":"</font>"
//                }
//            }
//        }

        request.source().highlighter(new HighlightBuilder().field("name").preTags("<font class=\"skcolor_ljg\">").postTags("</font>").requireFieldMatch(false));


        //查询
        // 3.发送请求
        SearchResponse response = this.restHighLevelClient.search(request,RequestOptions.DEFAULT);

        //4、得到结果
        SearchHits hits = response.getHits();

        //5、得到总笔数
        long count=hits.getTotalHits().value;

        //6、得到明细
        List<EsHotel> arrList=new ArrayList<>();

        SearchHit[] hitsHits = hits.getHits();
        //7、循环再一次读取数据
        for (SearchHit  ht : hitsHits ) {
            // 获取文档source
            String json = ht.getSourceAsString();
            // 反序列化
            EsHotel esHotel = objectMapper.readValue(json, EsHotel.class);

            //还要解析高亮字段
//            "highlight" : {
//                "name" : [
//                     "<font class='skcolor_ljg'>如家</font>酒店(北京良乡西路店)"
//                  ]
//            }
            //把高亮字段的结果，替换内存中的原始的值
            Map<String, HighlightField> highlightFields = ht.getHighlightFields();
            if(highlightFields!=null && highlightFields.containsKey("name")){
                HighlightField nameField = highlightFields.get("name");
                Text[] fragments = nameField.getFragments();
                String nameValue=fragments[0].string();
                esHotel.setName(nameValue);
            }

            arrList.add(esHotel);
        }

        //装组对象
        HotelResp resp=new HotelResp();
        resp.setTotal(count);
        resp.setRows(arrList);

        return resp;
    }

}
